﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace FairyGUI.Utils
{
	public enum XMLTagType
	{
		Start,
		End,
		Void,
		CDATA,
		Comment,
		Instruction
	}

	/// <summary>
	/// 
	/// </summary>
	public class XMLIterator
	{
		public static string tagName;
		public static XMLTagType tagType;
		public static string lastTagName;

		static string source;
		static int sourceLen;
		static int parsePos;
		static int tagPos;
		static int tagLength;
		static int lastTagEnd;
		static bool attrParsed;
		static bool lowerCaseName;
		static StringBuilder buffer = new StringBuilder();
		static Dictionary<string, string> attributes = new Dictionary<string, string>();

		const string CDATA_START = "<![CDATA[";
		const string CDATA_END = "]]>";
		const string COMMENT_START = "<!--";
		const string COMMENT_END = "-->";

		public static void Begin(string source, bool lowerCaseName = false)
		{
			XMLIterator.source = source;
			XMLIterator.lowerCaseName = lowerCaseName;
			sourceLen = source.Length;
			parsePos = 0;
			lastTagEnd = 0;
			tagPos = 0;
			tagLength = 0;
			tagName = null;
		}

		public static bool NextTag()
		{
			int pos;
			char c;
			tagType = XMLTagType.Start;
			buffer.Length = 0;
			lastTagEnd = parsePos;
			attrParsed = false;
			lastTagName = tagName;

			while ((pos = source.IndexOf('<', parsePos)) != -1)
			{
				parsePos = pos;
				pos++;

				if (pos == sourceLen)
					break;

				c = source[pos];
				if (c == '!')
				{
					if (sourceLen > pos + 7 && source.Substring(pos - 1, 9) == CDATA_START)
					{
						pos = source.IndexOf(CDATA_END, pos);
						tagType = XMLTagType.CDATA;
						tagName = string.Empty;
						tagPos = parsePos;
						if (pos == -1)
							tagLength = sourceLen - parsePos;
						else
							tagLength = pos + 3 - parsePos;
						parsePos += tagLength;
						return true;
					}
					else if (sourceLen > pos + 2 && source.Substring(pos - 1, 4) == COMMENT_START)
					{
						pos = source.IndexOf(COMMENT_END, pos);
						tagType = XMLTagType.Comment;
						tagName = string.Empty;
						tagPos = parsePos;
						if (pos == -1)
							tagLength = sourceLen - parsePos;
						else
							tagLength = pos + 3 - parsePos;
						parsePos += tagLength;
						return true;
					}
					else
					{
						pos++;
						tagType = XMLTagType.Instruction;
					}
				}
				else if (c == '/')
				{
					pos++;
					tagType = XMLTagType.End;
				}
				else if (c == '?')
				{
					pos++;
					tagType = XMLTagType.Instruction;
				}

				for (; pos < sourceLen; pos++)
				{
					c = source[pos];
					if (Char.IsWhiteSpace(c) || c == '>' || c == '/')
						break;
				}
				if (pos == sourceLen)
					break;

				buffer.Append(source, parsePos + 1, pos - parsePos - 1);
				if (buffer.Length > 0 && buffer[0] == '/')
					buffer.Remove(0, 1);

				bool singleQuoted = false, doubleQuoted = false;
				int possibleEnd = -1;
				for (; pos < sourceLen; pos++)
				{
					c = source[pos];
					if (c == '"')
					{
						if (!singleQuoted)
							doubleQuoted = !doubleQuoted;
					}
					else if (c == '\'')
					{
						if (!doubleQuoted)
							singleQuoted = !singleQuoted;
					}

					if (c == '>')
					{
						if (!(singleQuoted || doubleQuoted))
						{
							possibleEnd = -1;
							break;
						}

						possibleEnd = pos;
					}
					else if (c == '<')
						break;
				}
				if (possibleEnd != -1)
					pos = possibleEnd;

				if (pos == sourceLen)
					break;

				if (source[pos - 1] == '/')
					tagType = XMLTagType.Void;

				tagName = buffer.ToString();
				if (lowerCaseName)
					tagName = tagName.ToLower();
				tagPos = parsePos;
				tagLength = pos + 1 - parsePos;
				parsePos += tagLength;

				return true;
			}

			tagPos = sourceLen;
			tagLength = 0;
			tagName = null;
			return false;
		}

		public static string GetTagSource()
		{
			return source.Substring(tagPos, tagLength);
		}

		public static string GetRawText(bool trim = false)
		{
			if (lastTagEnd == tagPos)
				return string.Empty;
			else if (trim)
			{
				int i = lastTagEnd;
				for (; i < tagPos; i++)
				{
					char c = source[i];
					if (!char.IsWhiteSpace(c))
						break;
				}

				if (i == tagPos)
					return string.Empty;
				else
					return source.Substring(i, tagPos - i).TrimEnd();
			}
			else
				return source.Substring(lastTagEnd, tagPos - lastTagEnd);
		}

		public static string GetText(bool trim = false)
		{
			if (lastTagEnd == tagPos)
				return string.Empty;
			else if (trim)
			{
				int i = lastTagEnd;
				for (; i < tagPos; i++)
				{
					char c = source[i];
					if (!char.IsWhiteSpace(c))
						break;
				}

				if (i == tagPos)
					return string.Empty;
				else
					return XMLUtils.DecodeString(source.Substring(i, tagPos - i).TrimEnd());
			}
			else
				return XMLUtils.DecodeString(source.Substring(lastTagEnd, tagPos - lastTagEnd));
		}

		public static bool HasAttribute(string attrName)
		{
			if (!attrParsed)
			{
				attributes.Clear();
				ParseAttributes(attributes);
				attrParsed = true;
			}

			return attributes.ContainsKey(attrName);
		}

		public static string GetAttribute(string attrName)
		{
			if (!attrParsed)
			{
				attributes.Clear();
				ParseAttributes(attributes);
				attrParsed = true;
			}

			string value;
			if (attributes.TryGetValue(attrName, out value))
				return value;
			else
				return null;
		}

		public static string GetAttribute(string attrName, string defValue)
		{
			string ret = GetAttribute(attrName);
			if (ret != null)
				return ret;
			else
				return defValue;
		}

		public static int GetAttributeInt(string attrName)
		{
			return GetAttributeInt(attrName, 0);
		}

		public static int GetAttributeInt(string attrName, int defValue)
		{
			string value = GetAttribute(attrName);
			if (value == null || value.Length == 0)
				return defValue;

			int ret;
			if (int.TryParse(value, out ret))
				return ret;
			else
				return defValue;
		}

		public static float GetAttributeFloat(string attrName)
		{
			return GetAttributeFloat(attrName, 0);
		}

		public static float GetAttributeFloat(string attrName, float defValue)
		{
			string value = GetAttribute(attrName);
			if (value == null || value.Length == 0)
				return defValue;

			float ret;
			if (float.TryParse(value, out ret))
				return ret;
			else
				return defValue;
		}

		public static bool GetAttributeBool(string attrName)
		{
			return GetAttributeBool(attrName, false);
		}

		public static bool GetAttributeBool(string attrName, bool defValue)
		{
			string value = GetAttribute(attrName);
			if (value == null || value.Length == 0)
				return defValue;

			bool ret;
			if (bool.TryParse(value, out ret))
				return ret;
			else
				return defValue;
		}

		public static Dictionary<string, string> GetAttributes(Dictionary<string, string> result)
		{
			if (result == null)
				result = new Dictionary<string, string>();

			if (attrParsed)
			{
				foreach (KeyValuePair<string, string> kv in attributes)
					result[kv.Key] = kv.Value;
			}
			else //这里没有先ParseAttributes再赋值给result是为了节省复制的操作
				ParseAttributes(result);

			return result;
		}

		public static Hashtable GetAttributes(Hashtable result)
		{
			if (result == null)
				result = new Hashtable();

			if (attrParsed)
			{
				foreach (KeyValuePair<string, string> kv in attributes)
					result[kv.Key] = kv.Value;
			}
			else //这里没有先ParseAttributes再赋值给result是为了节省复制的操作
				ParseAttributes(result);

			return result;
		}

		static void ParseAttributes(IDictionary attrs)
		{
			string attrName;
			int valueStart;
			int valueEnd;
			bool waitValue = false;
			int quoted;
			buffer.Length = 0;
			int i = tagPos;
			int attrEnd = tagPos + tagLength;

			if (i < attrEnd && source[i] == '<')
			{
				for (; i < attrEnd; i++)
				{
					char c = source[i];
					if (Char.IsWhiteSpace(c) || c == '>' || c == '/')
						break;
				}
			}

			for (; i < attrEnd; i++)
			{
				char c = source[i];
				if (c == '=')
				{
					valueStart = -1;
					valueEnd = -1;
					quoted = 0;
					for (int j = i + 1; j < attrEnd; j++)
					{
						char c2 = source[j];
						if (Char.IsWhiteSpace(c2))
						{
							if (valueStart != -1 && quoted == 0)
							{
								valueEnd = j - 1;
								break;
							}
						}
						else if (c2 == '>')
						{
							if (quoted == 0)
							{
								valueEnd = j - 1;
								break;
							}
						}
						else if (c2 == '"')
						{
							if (valueStart != -1)
							{
								if (quoted != 1)
								{
									valueEnd = j - 1;
									break;
								}
							}
							else
							{
								quoted = 2;
								valueStart = j + 1;
							}
						}
						else if (c2 == '\'')
						{
							if (valueStart != -1)
							{
								if (quoted != 2)
								{
									valueEnd = j - 1;
									break;
								}
							}
							else
							{
								quoted = 1;
								valueStart = j + 1;
							}
						}
						else if (valueStart == -1)
						{
							valueStart = j;
						}
					}

					if (valueStart != -1 && valueEnd != -1)
					{
						attrName = buffer.ToString();
						if (lowerCaseName)
							attrName = attrName.ToLower();
						buffer.Length = 0;
						attrs[attrName] = XMLUtils.DecodeString(source.Substring(valueStart, valueEnd - valueStart + 1));
						i = valueEnd + 1;
					}
					else
						break;
				}
				else if (!Char.IsWhiteSpace(c))
				{
					if (waitValue || c == '/' || c == '>')
					{
						if (buffer.Length > 0)
						{
							attrName = buffer.ToString();
							if (lowerCaseName)
								attrName = attrName.ToLower();
							attrs[attrName] = string.Empty;
							buffer.Length = 0;
						}

						waitValue = false;
					}

					if (c != '/' && c != '>')
						buffer.Append(c);
				}
				else
				{
					if (buffer.Length > 0)
						waitValue = true;
				}
			}
		}
	}
}
